package project.web;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Locale;
import java.util.Map;

import online.model.ModelUtil;
import online.model.UniModel;
import online.model.session.ActionParameter;
import online.model.session.SessionUser;
import online.struts.action.UniForm;
import core.config.Factory;


/**
 * Factoryクラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class InstanceFactory {

	/**
	 * コンストラクタ
	 *
	 */
	private InstanceFactory() {
		throw new AssertionError();
	}

	/**
	 * インスタンス作成
	 * @param <T> ジェネリックス
	 * @param cls サービスクラス
	 * @param model 汎用モデル
	 * @return インスタンス
	 */
	public static <T> T create(final Class<T> cls, final UniForm model) {
		ActionParameter ap = null;
		SessionUser su = null;
		if (model != null) {
			ap = model.getActionParameter();
			su = model.getSessionUser();
		}
		return create(cls, model, ap, su);
	}

	/**
	 * インスタンス作成
	 * @param <T> ジェネリックス
	 * @param cls サービスクラス
	 * @param model 汎用モデル
	 * @param ap アクションパラメタ
	 * @param su セションユーザ情報
	 * @return インスタンス
	 */
	public static <T> T create(final Class<T> cls, final UniModel model,
					final ActionParameter ap, final SessionUser su) {
		T instance = Factory.create(cls);

		if (model != null) {
			ModelUtil.setModelValue(instance, model);
			setObjectTo(instance, model, "setUniModel", UniModel.class);
		}
		if (ap != null) {
			setPropertiesTo(instance, ap.getProperty());
			setObjectTo(instance, ap.getProperty(), "setProperty", Map.class);
			setObjectTo(instance, ap.getAid(), "setAid", String.class);
			setObjectTo(instance, ap.getDateTime(), "setDateTime", Date.class);
			setObjectTo(instance, ap.getDateTime(), "setDateTime", Timestamp.class);
			setObjectTo(instance, ap.getGid(), "setGid", String.class);
			setObjectTo(instance, ap.getIp(), "setIp", String.class);
			setObjectTo(instance, ap.getToken(), "setToken", String.class);
			setObjectTo(instance, ap, "setActionParameter", ActionParameter.class);
		}
		if (su != null) {
			setPropertiesTo(instance, su.getAttributeMap());
			setObjectTo(instance, su.getAttributeMap(), "setAttributeMap", Map.class);
			setObjectTo(instance, su.getUid(), "setUid", String.class);
			setObjectTo(instance, su, "setSessionUser", SessionUser.class);
		}

		return instance;
	}

	/**
	 * Beanにオブジェクトを設定します。
	 * @param bean 設定対象Bean
	 * @param obj 設定モデル
	 * @param method 設定メソッド名
	 * @param param パラメタクラス
	 */
	private static void setObjectTo(final Object bean, final Object obj,
					final String method, final Class<?>... param) {
		Method m = Factory.getMethod(bean.getClass(), method, param);
		Factory.invoke(bean, m, obj);
	}

	/**
	 * プロパティ設定
	 * @param bean 設定対象Bean
	 * @param prop プロパティ
	 */
	private static void setPropertiesTo(final Object bean, final Map<String, String> prop) {
		for (final Method mt : bean.getClass().getMethods()) {
			if (Modifier.isPublic(mt.getModifiers()) && mt.getName().startsWith("set")
							&& !Modifier.isStatic(mt.getModifiers()) && !mt.isBridge()
							&& !mt.isSynthetic() && mt.getParameterCount() == 1
							&& !Object.class.equals(mt.getDeclaringClass())) {
				Object obj = getPropertyValue(prop, mt.getName().substring("set".length()));
				if (obj != null) {
					Class<?> cls = mt.getParameterTypes()[0];
					if (String.class.equals(cls)) {
						Factory.invoke(bean, mt, obj);
					} else {
						obj = Factory.construct(Factory.getConstructor(cls, String.class), obj);
						if (obj != null) {
							Factory.invoke(bean, mt, obj);
						}
					}
				}
			}
		}
	}

	/**
	 * プロパティ値取得
	 * @param prop プロパティマップ
	 * @param name 名前
	 * @return プロパティ値
	 */
	private static String getPropertyValue(final Map<String, String> prop, final String name) {
		String ret = prop.get(name);
		if (ret == null) {
			int loc = name.offsetByCodePoints(0, 1);
			String head = name.substring(0, loc);
			String tail = name.substring(loc);
			ret = prop.get(head.toLowerCase(Locale.ENGLISH) + tail);
		}
		return ret;
	}
}
